﻿
namespace Patterns4Net.ArchitectureExplorer.ViewModels.Services
{
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using Mono.Cecil;
    using PatternEnforcer.Core;
    using Patterns4Net.ArchitectureExplorer.Diagram;
    using Patterns4Net.ArchitectureExplorer.Diagram.Services;
    using Patterns4Net.Core.CecilExtensions;

    [Export(typeof(PatternEnforcerWrapper))]
    public class PatternEnforcerWrapper
    {
        private readonly ErrorsWindowViewModel errorsWindow;

        private readonly OutputWindowViewModel outputWindow;

        private readonly PatternEnforcer patternEnforcer;

        [ImportingConstructor]
        public PatternEnforcerWrapper(
            ErrorsWindowViewModel errorsWindow,
            OutputWindowViewModel outputWindow,
            PatternEnforcer patternEnforcer)
        {
            this.errorsWindow = errorsWindow;
            this.outputWindow = outputWindow;
            this.patternEnforcer = patternEnforcer;
        }

        public void RunPatternEnforcer(Diagram diagram)
        {
            var writer = this.outputWindow.GetWriter();
            this.errorsWindow.Clear();
            writer.WriteLine("Pattern Enforcer has started...");

            var results = new List<PatternEnforcer.ResultItem>();
            var assemblies = diagram.GetTag<CecilDiagramLoader.AssembliesList>();
            if (assemblies == null)
            {
                writer.WriteLine("No assembly found.\nPattern Enforcer has finished...");
                return;
            }

            foreach (var assembly in assemblies)
            {
                writer.WriteLine("Checking assembly {0} ...", assembly.Name);
                results.AddRange(this.patternEnforcer.CheckAssembly(assembly));
            }

            foreach (var resultItem in results)
            {
                if (resultItem.IsValid)
                {
                    continue;
                }

                foreach (var patternAndResult in resultItem.CheckerResults)
                {
                    foreach (var checkerError in patternAndResult.Value.Errors)
                    {
                        var errorViewModel = (ErrorViewModel)CreateErrorMessage((dynamic)resultItem.Member, diagram);
                        errorViewModel.PatternName = patternAndResult.Key.GetType().Name;
                        errorViewModel.ErrorMessage = checkerError.Message;
                        this.errorsWindow.AddError(errorViewModel);
                    }
                }
            }

            writer.WriteLine("Pattern Enforcer has finished...");
        }

        private static ErrorViewModel CreateErrorMessage(MemberReference member, Diagram diagram)
        {
            Contract.Assert(false, string.Format("Cannot create a ErrorViewModel for member of type {0}", member.Name));
            return new ErrorViewModel();
        }

        private static ErrorViewModel CreateErrorMessage(TypeDefinition type, Diagram diagram)
        {
            return new ErrorViewModel
                {
                    TypeName = type.Name, 
                    MethodName = string.Empty,
                    DiagramElement = diagram.Types.FirstOrDefault(t =>
                        {
                            TypeDefinition typeDef;
                            return t.TryGetTag(out typeDef) && typeDef.IsEqual(type);
                        })
                };
        }

        private static ErrorViewModel CreateErrorMessage(MethodReference method, Diagram diagram)
        {
            return new ErrorViewModel
                {
                    TypeName = method.DeclaringType.Name,
                    MethodName = method.Name,
                    DiagramElement = diagram.Types.FirstOrDefault(t =>
                        {
                            TypeDefinition typeDef;
                            return t.TryGetTag(out typeDef) && typeDef.IsEqual(method.DeclaringType);
                        })
                };
        }
    }
}
